package com.amap.flutter.map;

import android.content.Context;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;

import com.amap.api.location.AMapLocationClient;
import com.amap.api.location.CoordinateConverter;
import com.amap.api.location.DPoint;
import com.amap.flutter.map.location.AMapLocationClientImpl;
import com.amap.flutter.map.utils.LogUtil;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;

/**
 * AmapFlutterMapPlugin
 */
public class AMapFlutterMapPlugin implements
        FlutterPlugin,
        ActivityAware, MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
    private static final String CLASS_NAME = "AMapFlutterMapPlugin";
    private FlutterPluginBinding pluginBinding;
    private Lifecycle lifecycle;

    private static final String VIEW_TYPE = "com.amap.flutter.map";

    private static final String CHANNEL_METHOD_LOCATION = "amap_flutter_location";
    private static final String CHANNEL_STREAM_LOCATION = "amap_flutter_location_stream";
    private Context mContext = null;
    public static EventChannel.EventSink mEventSink = null;
    private Map<String, AMapLocationClientImpl> locationClientMap = new ConcurrentHashMap<String, AMapLocationClientImpl>(8);

    public AMapFlutterMapPlugin() {
    }

    // FlutterPlugin

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
        LogUtil.i(CLASS_NAME, "onAttachedToEngine==>");
        pluginBinding = binding;
        binding
                .getPlatformViewRegistry()
                .registerViewFactory(
                        VIEW_TYPE,
                        new AMapPlatformViewFactory(
                                binding.getBinaryMessenger(),
                                new LifecycleProvider() {
                                    @Nullable
                                    @Override
                                    public Lifecycle getLifecycle() {
                                        return lifecycle;
                                    }
                                }));
        if (null == mContext) {
            mContext = binding.getApplicationContext();

            /**
             * 方法调用通道
             */
            final MethodChannel channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_METHOD_LOCATION);
            channel.setMethodCallHandler(this);

            /**
             * 回调监听通道
             */
            final EventChannel eventChannel = new EventChannel(binding.getBinaryMessenger(), CHANNEL_STREAM_LOCATION);
            eventChannel.setStreamHandler(this);
        }
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        LogUtil.i(CLASS_NAME, "onDetachedFromEngine==>");
        pluginBinding = null;

        for (Map.Entry<String, AMapLocationClientImpl> entry : locationClientMap.entrySet()) {
            entry.getValue().destroy();
        }
    }


    // ActivityAware

    @Override
    public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
        LogUtil.i(CLASS_NAME, "onAttachedToActivity==>");
        lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
    }

    @Override
    public void onDetachedFromActivity() {
        LogUtil.i(CLASS_NAME, "onDetachedFromActivity==>");
        lifecycle = null;
    }

    @Override
    public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
        LogUtil.i(CLASS_NAME, "onReattachedToActivityForConfigChanges==>");
        onAttachedToActivity(binding);
    }

    @Override
    public void onDetachedFromActivityForConfigChanges() {
        LogUtil.i(CLASS_NAME, "onDetachedFromActivityForConfigChanges==>");
        this.onDetachedFromActivity();
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        switch (call.method) {
            case "updatePrivacyStatement":
                updatePrivacyStatement((Map) call.arguments);
                break;
            case "setApiKey":
                setApiKey((Map) call.arguments);
                break;
            case "setLocationOption":
                setLocationOption((Map) call.arguments);
                break;
            case "startLocation":
                startLocation((Map) call.arguments);
                break;
            case "stopLocation":
                stopLocation((Map) call.arguments);
                break;
            case "destroy":
                destroy((Map) call.arguments);
                break;
            case "calculateLineDistance":
                result.success(calculateLineDistance((Map) call.arguments));
                break;
            case "calculateLineDistanceBatch":
                result.success(calculateLineDistanceBatch((Map) call.arguments));
                break;
            default:
                result.notImplemented();
                break;

        }
    }

    private float calculateLineDistance(Map argMap) {
        DPoint start = new DPoint((Double) argMap.get("startLatitude"), (Double) argMap.get("startLongitude"));
        DPoint end = new DPoint((Double) argMap.get("endLatitude"), (Double) argMap.get("endLongitude"));
        return CoordinateConverter.calculateLineDistance(start, end);
    }

    private List<Float> calculateLineDistanceBatch(Map argMap) {
        DPoint start = new DPoint((Double) argMap.get("latitude"), (Double) argMap.get("longitude"));
        ArrayList<Map> list = (ArrayList<Map>) argMap.get("list");
        List<Float> results = list.stream().map(item -> {
            DPoint end = new DPoint((Double) item.get("latitude"), (Double) item.get("longitude"));
            return CoordinateConverter.calculateLineDistance(start, end);
        }).collect(Collectors.toList());
        return results;
    }

    @Override
    public void onListen(Object arguments, EventChannel.EventSink events) {
        mEventSink = events;
    }

    @Override
    public void onCancel(Object arguments) {
        for (Map.Entry<String, AMapLocationClientImpl> entry : locationClientMap.entrySet()) {
            entry.getValue().stopLocation();
        }
    }

    /**
     * 开始定位
     */
    private void startLocation(Map argsMap) {
        AMapLocationClientImpl locationClientImp = getLocationClientImp(argsMap);
        if (null != locationClientImp) {
            locationClientImp.startLocation(argsMap);
        }
    }


    /**
     * 停止定位
     */
    private void stopLocation(Map argsMap) {
        AMapLocationClientImpl locationClientImp = getLocationClientImp(argsMap);
        if (null != locationClientImp) {
            locationClientImp.stopLocation();
        }
    }

    /**
     * 销毁
     *
     * @param argsMap
     */
    private void destroy(Map argsMap) {
        AMapLocationClientImpl locationClientImp = getLocationClientImp(argsMap);
        if (null != locationClientImp) {
            locationClientImp.destroy();

            locationClientMap.remove(getPluginKeyFromArgs(argsMap));
        }
    }

    /**
     * 设置apikey
     *
     * @param apiKeyMap
     */
    private void setApiKey(Map apiKeyMap) {
        if (null != apiKeyMap) {
            if (apiKeyMap.containsKey("android")
                    && !TextUtils.isEmpty((String) apiKeyMap.get("android"))) {
                AMapLocationClient.setApiKey((String) apiKeyMap.get("android"));
            }
        }
    }

    private void updatePrivacyStatement(Map privacyShowMap) {
        if (null != privacyShowMap) {
            Class<AMapLocationClient> locationClazz = AMapLocationClient.class;

            if (privacyShowMap.containsKey("hasContains") && privacyShowMap.containsKey("hasShow")) {
                boolean hasContains = (boolean) privacyShowMap.get("hasContains");
                boolean hasShow = (boolean) privacyShowMap.get("hasShow");
                try {
                    Method showMethod = locationClazz.getMethod("updatePrivacyShow", Context.class, boolean.class, boolean.class);
                    ;
                    showMethod.invoke(null, mContext, hasContains, hasShow);
                } catch (Throwable e) {
//          e.printStackTrace();
                }
            }

            if (privacyShowMap.containsKey("hasAgree")) {
                boolean hasAgree = (boolean) privacyShowMap.get("hasAgree");
                try {
                    Method agreeMethod = locationClazz.getMethod("updatePrivacyAgree", Context.class, boolean.class);
                    agreeMethod.invoke(null, mContext, hasAgree);
                } catch (Throwable e) {
//            e.printStackTrace();
                }
            }
        }
    }

    /**
     * 设置定位参数
     *
     * @param argsMap
     */
    private void setLocationOption(Map argsMap) {
        AMapLocationClientImpl locationClientImp = getLocationClientImp(argsMap);
        if (null != locationClientImp) {
            locationClientImp.setLocationOption(argsMap);
        }
    }

    private AMapLocationClientImpl getLocationClientImp(Map argsMap) {
        if (null == locationClientMap) {
            locationClientMap = new ConcurrentHashMap<String, AMapLocationClientImpl>(8);
        }

        String pluginKey = getPluginKeyFromArgs(argsMap);
        if (TextUtils.isEmpty(pluginKey)) {
            return null;
        }

        if (!locationClientMap.containsKey(pluginKey)) {
            AMapLocationClientImpl locationClientImp = new AMapLocationClientImpl(mContext, pluginKey, mEventSink);
            locationClientMap.put(pluginKey, locationClientImp);
        }
        return locationClientMap.get(pluginKey);
    }

    private String getPluginKeyFromArgs(Map argsMap) {
        String pluginKey = null;
        try {
            if (null != argsMap) {
                pluginKey = (String) argsMap.get("pluginKey");
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return pluginKey;
    }
}
